Etapas¶

  1. Entendimento do Problema;
  2. Aquisição e Entendimento dos Dados;
  3. Tratamento dos Dados;
  4. Análise Exploratória de Dados;
  5. Segmentando a base com K-Means;
  6. Definição da Arquitetura, Criação da Rede e Execução do Modelo;
  7. Avaliando os Modelos: Sumários, Métricas e Conclusões.

1. Entendimento do Problema¶

  • Para esse projeto tive o desejo de aprender a unir algoritmos não-supervisionados com supervisionados, rotulando uma base para depois treinar um algoritmo que possa predizer esses rótulos em novos casos, como estou em uma etapa de aprendizado em que estou focando em deep learning optei por inserir uma rede neural que possa classificar esses rótulos;
  • Fiz algumas pesquisas em datasets de áreas do conhecimento que possam me interessar e me deparei com o dataset San Francisco Police Department Incidents do Google BigQuery, esse dataset contém mais de 2 milhões de incidentes que vão dos anos de 2003 a 2018 e temos informações da categoria do crime, descrições, datas e resoluções de cada incidente;
  • Serão realizados tratamentos e feature engineering para a EDA;
  • Na parte de Análise Exploratória pretendo explorar a fundo os principais casos, principais resoluções, análises temporais e análises geográficas com criação de heatmaps em mapas de rua mostrando os locais com mais incidentes e informações úteis sobre esses incidentes (Mapas Interativos);
  • Ao fim essa base será rotulada e uma rede neural será criada e treinada para predizer os rótulos, em ambos algorítmos serão testados diferentes hiperparâmetros para a execução de um modelo que traga melhores resultados.

Data Dictionary¶

Feature Data Type Description
unique_key INTEGER Identificador único para cada incidente de crime registrado.
category STRING Categoria geral do crime cometido (e.g., "ASSAULT", "ROBBERY").
descript STRING Descrição detalhada do crime (e.g., "GRAND THEFT FROM LOCKED AUTO").
dayofweek STRING Dia da semana em que o crime ocorreu (e.g., "Monday", "Tuesday").
pddistrict STRING Distrito do Departamento de Polícia onde o crime foi registrado.
resolution STRING Resultado ou resolução do incidente (e.g., "ARREST, BOOKED", "NONE").
address STRING Endereço onde o crime ocorreu.
longitude FLOAT Coordenada geográfica de longitude do local onde o crime ocorreu.
latitude FLOAT Coordenada geográfica de latitude do local onde o crime ocorreu.
location STRING Localização combinada em formato de string (Latitude/Longitude).
pdid INTEGER Identificador único para operações de atualização e inserção no dataset.
timestamp TIMESTAMP Data e hora em que o crime foi registrado.

2. Aquisição e Entendimento dos Dados¶

In [5]:
from google.cloud import bigquery
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.graph_objects as go
import plotly.express as px
import plotly.colors as pc
#import scipy.stats as stats
import re

import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "composite-keel-371920-24c2737df989.json"

import warnings
warnings.filterwarnings('ignore')

# Configurando objetos Pandas DataFrame e Numpy Arrays para exibirem todas as informações
np.set_printoptions(threshold=np.inf)
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)

# Configurando estilo e paleta de cores que serão utilizadas nos gráficos
sns.set_theme(style="whitegrid", palette="viridis")
sns.color_palette('viridis')
Out[5]:
In [6]:
# Configurar o cliente BigQuery
client = bigquery.Client()

# Definir a consulta SQL
query = f'''SELECT * FROM `bigquery-public-data.san_francisco.sfpd_incidents`'''

# Executar a consulta e obter os resultados
query_job = client.query(query)
df_big_query = query_job.to_dataframe()

# Salvando em csv
df_big_query.to_csv('sfpd_incidents.csv')

Leitura dos Dados¶

In [8]:
df = pd.read_csv('sfpd_incidents.csv')
df.drop('Unnamed: 0', axis='columns', inplace=True)
In [9]:
df.head(2)
Out[9]:
unique_key category descript dayofweek pddistrict resolution address longitude latitude location pdid timestamp
0 50688521 VANDALISM MALICIOUS MISCHIEF, GRAFFITI Thursday PARK NONE 1400 Block of WALLER ST -122.447968 37.768863 (37.768862804, -122.44796773714) 5068852128165 2005-06-16 00:01:00+00:00
1 30738083 LARCENY/THEFT PETTY THEFT FROM LOCKED AUTO Tuesday PARK NONE 200 Block of CRESTMONT DR -122.459716 37.756650 (37.75664997762, -122.45971647828) 3073808306243 2003-06-17 18:30:00+00:00

Informações Gerais dos Dados¶

In [11]:
df.info(verbose=True, show_counts=True)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2170785 entries, 0 to 2170784
Data columns (total 12 columns):
 #   Column      Non-Null Count    Dtype  
---  ------      --------------    -----  
 0   unique_key  2170785 non-null  int64  
 1   category    2170785 non-null  object 
 2   descript    2170785 non-null  object 
 3   dayofweek   2170785 non-null  object 
 4   pddistrict  2170784 non-null  object 
 5   resolution  2170785 non-null  object 
 6   address     2170785 non-null  object 
 7   longitude   2170785 non-null  float64
 8   latitude    2170785 non-null  float64
 9   location    2170785 non-null  object 
 10  pdid        2170785 non-null  int64  
 11  timestamp   2170785 non-null  object 
dtypes: float64(2), int64(2), object(8)
memory usage: 198.7+ MB

Verificação de Dados Nulos¶

In [13]:
plt.figure(figsize=(3,2))
sns.heatmap(df.isnull(),
            yticklabels=False,
            cbar=False,
            cmap='viridis')
df.isnull().sum() / len(df)
Out[13]:
unique_key    0.000000e+00
category      0.000000e+00
descript      0.000000e+00
dayofweek     0.000000e+00
pddistrict    4.606628e-07
resolution    0.000000e+00
address       0.000000e+00
longitude     0.000000e+00
latitude      0.000000e+00
location      0.000000e+00
pdid          0.000000e+00
timestamp     0.000000e+00
dtype: float64
No description has been provided for this image

Verificação de Dados Infinitos¶

In [15]:
plt.figure(figsize=(3,2))
sns.heatmap(np.isinf(df.select_dtypes(include=['number'])),
            yticklabels=False,
            cbar=False,
            cmap='viridis')
np.isinf(df.select_dtypes(include=['number'])).sum()
Out[15]:
unique_key    0
longitude     0
latitude      0
pdid          0
dtype: int64
No description has been provided for this image

Dados Categóricos - Verificando distribuição e existência de valores fora do domínio esperado¶

In [17]:
columns = ['category','dayofweek','pddistrict','resolution']
In [18]:
for column in columns:
    display(df[column].value_counts())
    print(f'\n\n')
category
LARCENY/THEFT                  467657
OTHER OFFENSES                 304042
NON-CRIMINAL                   233323
ASSAULT                        190394
VEHICLE THEFT                  125209
DRUG/NARCOTIC                  118260
VANDALISM                      113436
WARRANTS                        99799
BURGLARY                        89528
SUSPICIOUS OCC                  78823
MISSING PERSON                  63706
ROBBERY                         54854
FRAUD                           40733
SECONDARY CODES                 25223
FORGERY/COUNTERFEITING          22839
WEAPON LAWS                     21698
TRESPASS                        18959
PROSTITUTION                    16652
STOLEN PROPERTY                 11645
SEX OFFENSES, FORCIBLE          11411
DISORDERLY CONDUCT               9950
DRUNKENNESS                      9746
RECOVERED VEHICLE                8714
DRIVING UNDER THE INFLUENCE      5593
KIDNAPPING                       5275
RUNAWAY                          4355
LIQUOR LAWS                      4069
ARSON                            3839
EMBEZZLEMENT                     2927
LOITERING                        2414
SUICIDE                          1275
FAMILY OFFENSES                  1171
BAD CHECKS                        914
BRIBERY                           797
EXTORTION                         725
SEX OFFENSES, NON FORCIBLE        420
GAMBLING                          341
PORNOGRAPHY/OBSCENE MAT            55
TREA                               14
Name: count, dtype: int64


dayofweek
Friday       331007
Wednesday    317739
Saturday     313869
Thursday     309655
Tuesday      309179
Monday       300609
Sunday       288727
Name: count, dtype: int64


pddistrict
SOUTHERN      391669
MISSION       293940
NORTHERN      266869
CENTRAL       220420
BAYVIEW       217097
INGLESIDE     190593
TENDERLOIN    189017
TARAVAL       163620
PARK          123110
RICHMOND      114449
Name: count, dtype: int64


resolution
NONE                                      1356705
ARREST, BOOKED                             514120
ARREST, CITED                              154784
LOCATED                                     34463
PSYCHOPATHIC CASE                           29185
UNFOUNDED                                   23517
JUVENILE BOOKED                             13907
COMPLAINANT REFUSES TO PROSECUTE             8089
DISTRICT ATTORNEY REFUSES TO PROSECUTE       7955
NOT PROSECUTED                               7720
JUVENILE CITED                               6586
PROSECUTED BY OUTSIDE AGENCY                 5070
EXCEPTIONAL CLEARANCE                        4224
JUVENILE ADMONISHED                          3004
JUVENILE DIVERTED                             694
CLEARED-CONTACT JUVENILE FOR MORE INFO        678
PROSECUTED FOR LESSER OFFENSE                  84
Name: count, dtype: int64


In [19]:
df['descript'].nunique()
Out[19]:
915
  • Foram encontradas 915 diferentes categorias para as descrições dos crimes, segue abaixo algumas amostras:
In [21]:
df['descript'].value_counts().sample(5)
Out[21]:
descript
ATTEMPTED THEFT OF A BICYCLE                            224
CABLE TV CONNECTION OR DECODING DEVICE, UNAUTHORIZED      6
SELLING/DISCHARGING OF FIRECRACKERS                      99
ATTEMPTED MAYHEM WITH A GUN                               3
ATTEMPTED SUICIDE BY FIREARMS                            19
Name: count, dtype: int64

3. Tratamento dos Dados¶

Nesta etapa será realizado:

  • Feature Engineering (timestamp): Conversão da variável tempo para o tipo datetime e extração do dia, mes, ano, hora e minuto em variáveis separadas para análises temporais;
  • Feature Engineering (address): Extração da rua e bloco para variáveis separadas com intuito de análises pontuais em determinadas regiões.
  • Feature Engineering (isweekend): Variável binária indicando se é final de semana ou não.
  • Foram localizados dados nulos na variável 'pddistrict', porém sua existência equivale a 4.606628e-07 do percentual total de dados. A princípio será removido para evitar possíveis problemas na execução de futuras análises.
In [23]:
df.dropna(subset=['pddistrict'], inplace=True)
In [24]:
df['is_weekend'] = df['dayofweek'].apply(lambda x: 1 if x in ['Saturday', 'Sunday'] else 0)
In [25]:
df['timestamp'] = pd.to_datetime(df['timestamp'])
In [26]:
df['timestamp'][:5]
Out[26]:
0   2005-06-16 00:01:00+00:00
1   2003-06-17 18:30:00+00:00
2   2013-01-16 08:30:00+00:00
3   2008-05-25 02:30:00+00:00
4   2007-11-05 20:06:00+00:00
Name: timestamp, dtype: datetime64[ns, UTC]
In [27]:
df['time'] = df['timestamp'].dt.time
df['day'] = df['timestamp'].dt.day
df['month'] = df['timestamp'].dt.month
df['year'] = df['timestamp'].dt.year
In [28]:
df['address'].sample(5)
Out[28]:
187355            HOWARD ST / 11TH ST
1403725    SANCHEZ ST / CUMBERLAND ST
1970797      1000 Block of POTRERO AV
1552783     FREDERICK ST / STANYAN ST
759385     2500 Block of GREENWICH ST
Name: address, dtype: object
In [29]:
# Função para extrair número e nome da rua
def extract_address_components(address):
    # Regex para capturar padrões de "Block" e ruas
    block_pattern = r'(\d+) Block of (\w+ \w+|\w+)(?: AV| DR| ST)'
    
    # Regex para capturar interseções (opcional)
    intersection_pattern = r'(\w+ \w+|\w+) / (\w+ \w+|\w+)'
    
    # Tentativa de combinar com padrões conhecidos
    if re.search(block_pattern, address):
        match = re.search(block_pattern, address)
        return pd.Series({'number': match.group(1), 'street': match.group(2)})
    elif re.search(intersection_pattern, address):
        match = re.search(intersection_pattern, address)
        # Aqui você poderia lidar com interseções, se necessário
        return pd.Series({'number': 'Not Informed', 'street': match.group(1) + " / " + match.group(2)})
    else:
        return pd.Series({'number': 'Not Informed', 'street': address})

# Aplicando a função para criar novas colunas
df[['number', 'street']] = df['address'].apply(extract_address_components)
In [30]:
# Reorganizando as colunas criadas
df = df[['unique_key', 'pdid', 'category', 'descript', 'resolution', 'pddistrict', 'address', 'number', 'street',
         'longitude', 'latitude', 'location', 'timestamp', 'dayofweek', 'time', 'day', 'month', 'year', 'is_weekend']]
df.sample(5)
Out[30]:
unique_key pdid category descript resolution pddistrict address number street longitude latitude location timestamp dayofweek time day month year is_weekend
1907783 50753243 5075324351040 NON-CRIMINAL AIDED CASE JUVENILE CITED CENTRAL STOCKTON ST / WASHINGTON ST Not Informed STOCKTON ST / WASHINGTON ST -122.408083 37.794941 (37.7949405880054, -122.408083155376) 2005-07-06 09:50:00+00:00 Wednesday 09:50:00 6 7 2005 0
8860 140395963 14039596304138 ASSAULT BATTERY, FORMER SPOUSE OR DATING RELATIONSHIP ARREST, BOOKED BAYVIEW BACON ST / SAN BRUNO AV Not Informed BACON ST / SAN BRUNO -122.403595 37.727634 (37.7276340992506, -122.403595293514) 2014-05-11 18:00:00+00:00 Sunday 18:00:00 11 5 2014 1
1911007 136125233 13612523306244 LARCENY/THEFT GRAND THEFT FROM LOCKED AUTO NONE CENTRAL 1500 Block of THE EMBARCADERONORTH ST 1500 THE EMBARCADERONORTH -122.408136 37.807860 (37.8078603496682, -122.408136108062) 2013-07-15 14:00:00+00:00 Monday 14:00:00 15 7 2013 0
520884 130347300 13034730006362 LARCENY/THEFT PETTY THEFT SHOPLIFTING NONE NORTHERN 1300 Block of FRANKLIN ST 1300 FRANKLIN -122.423329 37.786627 (37.7866266269211, -122.423329395229) 2013-04-28 15:40:00+00:00 Sunday 15:40:00 28 4 2013 1
1830610 90435984 9043598405013 BURGLARY BURGLARY OF APARTMENT HOUSE, UNLAWFUL ENTRY NONE TENDERLOIN 200 Block of JONES ST 200 JONES -122.412437 37.783486 (37.7834857455601, -122.412436901867) 2009-04-26 08:00:00+00:00 Sunday 08:00:00 26 4 2009 1

Salvando Mudanças¶

  • Este processo é necessário pois estou lidando com mais de 2 milhões de instâncias e o tempo de execução de certas células tem sido longo.
In [32]:
df.to_csv('sfpd_incidents.csv', index=False)
In [33]:
df = pd.read_csv('sfpd_incidents.csv')

4. Análise Exploratória de Dados¶

Nesta EDA serão focadas as seguintes análises:

  • Estatísticas Descritivas e Distribuição das Features, perguntas como: Quais crimes são mais comuns? Quais são as principais resoluções desses crimes?
  • Análises Geográficas utilizando plotly, perguntas como: Quais locais ocorreram mais crimes?
  • Análises Temporais com as features relacionadas ao tempo criadas na etapa anterior, perguntas como: Quais meses temos mais furtos? Quais horários temos mais incidentes acontecendo? Qual foi o ano que ocorreram mais crimes?

Estatísticas Descritivas e Distribuição das Features:¶

In [36]:
df.describe(include='object').transpose()
Out[36]:
count unique top freq
category 2170784 39 LARCENY/THEFT 467656
descript 2170784 915 GRAND THEFT FROM LOCKED AUTO 173078
resolution 2170784 17 NONE 1356704
pddistrict 2170784 10 SOUTHERN 391669
address 2170784 25123 800 Block of BRYANT ST 64271
number 2170784 75 Not Informed 749465
street 2170784 16656 BRYANT 69929
location 2170784 60767 (37.775420706711, -122.403404791479) 55669
timestamp 2170784 960695 2011-01-01 00:01:00+00:00 185
dayofweek 2170784 7 Friday 331007
time 2170784 1439 12:00:00 56155

Quais são os crimes mais comuns e quais resoluções esses crimes tiveram?¶

In [38]:
# Agrupar os dados pela categoria de crime e resolução
grouped_data_resolutions = df.groupby(['category', 'resolution']).size().reset_index(name='count')

# Selecionar as top 10 categorias de crimes mais comuns
top_10_categories = df['category'].value_counts().nlargest(25).index
grouped_data_resolutions = grouped_data_resolutions[grouped_data_resolutions['category'].isin(top_10_categories)]

# Definir uma paleta de 17 cores distintas
custom_colors = ['#636EFA', '#EF553B', '#00CC96', '#AB63FA', '#FFA15A', '#19D3F3', 
                 '#FF6692', '#B6E880', '#FF97FF', '#FECB52', '#1F77B4', '#FF7F0E', 
                 '#2CA02C', '#D62728', '#9467BD', '#8C564B', '#E377C2']

# Criar o gráfico de barras empilhadas com Plotly
fig = px.bar(grouped_data_resolutions.sort_values(by='count', ascending=False), 
             x='category', 
             y='count',
             color='resolution', 
             title='Distribuição de Resoluções por Categoria de Crime',
             labels={'category': 'Categoria de Crime', 'count': 'Número de Incidentes', 'resolution': 'Resolução'},
             color_discrete_sequence=custom_colors)  # Você pode escolher outras sequências de cores

# Personalizar o layout
fig.update_layout(barmode='stack',
                  height=650,
                  xaxis_title='Categoria de Crime',
                  yaxis_title='Número de Incidentes',
                  plot_bgcolor='white', 
                  yaxis=dict(showgrid=True, gridcolor='gray'))

# Exibir o gráfico
fig.show()
In [39]:
df['category'].value_counts()[:10]
Out[39]:
category
LARCENY/THEFT     467656
OTHER OFFENSES    304042
NON-CRIMINAL      233323
ASSAULT           190394
VEHICLE THEFT     125209
DRUG/NARCOTIC     118260
VANDALISM         113436
WARRANTS           99799
BURGLARY           89528
SUSPICIOUS OCC     78823
Name: count, dtype: int64

Nesses crimes mais comuns, em quais distritos eles ocorrem ?¶

In [41]:
# Agrupar os dados pela categoria de crime e distritos
grouped_data_district = df.groupby(['category', 'pddistrict']).size().reset_index(name='count')

# Selecionar as top 10 categorias de crimes mais comuns
top_10_categories = df['category'].value_counts().nlargest(25).index
grouped_data_district = grouped_data_district[grouped_data_district['category'].isin(top_10_categories)]

# Definir uma paleta de 17 cores distintas
custom_colors = ['#636EFA', '#EF553B', '#00CC96', '#AB63FA', '#FFA15A', '#19D3F3', 
                 '#FF6692', '#B6E880', '#FF97FF', '#FECB52', '#1F77B4', '#FF7F0E', 
                 '#2CA02C', '#D62728', '#9467BD', '#8C564B', '#E377C2']

# Criar o gráfico de barras empilhadas com Plotly
fig = px.bar(grouped_data_district.sort_values(by='count', ascending=False), 
             x='category', 
             y='count',
             color='pddistrict', 
             title='Distribuição de Distritos por Categoria de Crime',
             labels={'category': 'Categoria de Crime', 'count': 'Número de Incidentes', 'pddistrict': 'Distritos'},
             color_discrete_sequence=custom_colors)  # Você pode escolher outras sequências de cores

# Personalizar o layout
fig.update_layout(barmode='stack',
                  height=650,
                  xaxis_title='Categoria de Crime',
                  yaxis_title='Número de Incidentes',
                  plot_bgcolor='white', 
                  yaxis=dict(showgrid=True, gridcolor='gray'))

# Exibir o gráfico
fig.show()
In [42]:
df['pddistrict'].value_counts()
Out[42]:
pddistrict
SOUTHERN      391669
MISSION       293940
NORTHERN      266869
CENTRAL       220420
BAYVIEW       217097
INGLESIDE     190593
TENDERLOIN    189017
TARAVAL       163620
PARK          123110
RICHMOND      114449
Name: count, dtype: int64

Conclusões:¶

  • Southern District foi o local onde mais roubos, notoriamente também discussões onde ofensas foram proferidas também ocorreram. Este distrito também em relação aos outros ocorre diversos outros tipos de incidentes com uma escala superior a outros.
  • Em relação ao consumo e venda de drogas, o distrito de TENDERLIOIN lidera a quantidade de incidentes, com números acima de 37 mil incidentes.
  • No distrito de MISSION tivemos uma notável quantidade de assaltos, quase empatando com o distrito Sul.
In [44]:
df.head(1)
Out[44]:
unique_key pdid category descript resolution pddistrict address number street longitude latitude location timestamp dayofweek time day month year is_weekend
0 50688521 5068852128165 VANDALISM MALICIOUS MISCHIEF, GRAFFITI NONE PARK 1400 Block of WALLER ST 1400 WALLER -122.447968 37.768863 (37.768862804, -122.44796773714) 2005-06-16 00:01:00+00:00 Thursday 00:01:00 16 6 2005 0

Continuando nos crimes mais comuns, quais são suas principais descrições? (que tipo de assalto foi realizado por exemplo?)¶

In [46]:
# Agrupar os dados pela categoria de crime e descrição, e contar as ocorrências
grouped_data = df.groupby(['category', 'descript']).size().reset_index(name='count')

# Selecionar as top 25 categorias de crimes mais comuns
top_25_categories = df['category'].value_counts().nlargest(25).index

# Filtrar os dados para as 25 categorias mais comuns
filtered_data = grouped_data[grouped_data['category'].isin(top_25_categories)]

# Filtrar as top 10 descrições mais comuns para cada uma das 25 categorias principais
top_descriptions = []
for category in top_25_categories:
    category_data = filtered_data[filtered_data['category'] == category]
    top_descriptions.append(category_data.nlargest(10, 'count'))

# Concatenar todos os DataFrames em um só
grouped_data_top = pd.concat(top_descriptions).reset_index(drop=True)

# Criar o gráfico de barras empilhadas com Plotly
fig = px.bar(grouped_data_top.sort_values(by='count', ascending=False), 
             x='category', 
             y='count',
             color='descript', 
             title='Distribuição das Descrições Mais Comuns por Categoria de Crime',
             labels={'category': 'Categoria de Crime', 'count': 'Número de Incidentes', 'descript': 'Descrição do Crime'},
             color_discrete_sequence=custom_colors)

# Personalizar o layout
fig.update_layout(barmode='stack',
                  height=650,
                  xaxis_title='Categoria de Crime',
                  yaxis_title='Número de Incidentes')

# Exibir o gráfico
fig.show()
In [47]:
# Filtrar as top 10 descrições mais comuns para cada uma das 25 categorias principais
def get_top_descriptions(group, top_n=10):
    return group.nlargest(top_n, 'count')

grouped_data_descriptions = grouped_data.groupby('category', group_keys=False).apply(get_top_descriptions)

# Criar o gráfico de barras empilhadas com Plotly
fig = px.bar(grouped_data_top.sort_values(by='count', ascending=False), 
             x='category', 
             y='count',
             color='descript', 
             title='Distribuição das Descrições Mais Comuns por Categoria de Crime',
             labels={'category': 'Categoria de Crime', 'count': 'Número de Incidentes', 'descript': 'Descrição do Crime'},
             color_discrete_sequence=custom_colors)

# Personalizar o layout
fig.update_layout(barmode='stack',
                  height=650,
                  xaxis_title='Categoria de Crime',
                  yaxis_title='Número de Incidentes')

# Renderizar com tamanho fixo
from IPython.display import display, HTML

display(HTML('<style>.container { width:100% !important; }</style>'))

# Exibir o gráfico
fig.show()

Conclusões:¶

  • Neste gráfico é possível ver as principais descrições dos incidentes, notoriamente temos números acima de 173 mil roubos a carros, 62 mil casos de problemas de suspensão ou revogação de carteiras de motorista, 43 mil casos de vandalismo etc.

Furto e Roubo foram o tipo mais comum de crime cometido, quais tipos de furtos foram mais realizados?¶

In [50]:
df[df['category'] == 'LARCENY/THEFT']['descript'].value_counts()[:10]
Out[50]:
descript
GRAND THEFT FROM LOCKED AUTO      173078
PETTY THEFT FROM LOCKED AUTO       50976
PETTY THEFT OF PROPERTY            44655
GRAND THEFT OF PROPERTY            28574
PETTY THEFT FROM A BUILDING        25221
PETTY THEFT SHOPLIFTING            23937
GRAND THEFT FROM A BUILDING        22158
GRAND THEFT FROM PERSON            17691
GRAND THEFT FROM UNLOCKED AUTO     14744
GRAND THEFT PICKPOCKET             13414
Name: count, dtype: int64

Conclusões:¶

  • Furtos sobre carros são os mais frequentes seguidos de roubos em propriedades, notável também furtos em lojas

Análises Temporais¶

Intervalo de tempo que temos dados registrados¶

In [54]:
print(f'Temos registros de incidentes do ano de {df['year'].min()} a {df['year'].max()}')

df_year_incidents = df['year'].value_counts().reset_index()
df_year_incidents['Porcentagem do total'] = (df['year'].value_counts() / len(df)).values
df_year_incidents
Temos registros de incidentes do ano de 2003 a 2018
Out[54]:
year count Porcentagem do total
0 2015 156526 0.072106
1 2017 153667 0.070789
2 2013 152806 0.070392
3 2016 150884 0.069507
4 2014 150128 0.069158
5 2003 149176 0.068720
6 2004 148148 0.068246
7 2005 142186 0.065500
8 2008 141311 0.065097
9 2012 140847 0.064883
10 2009 139860 0.064428
11 2006 137853 0.063504
12 2007 137639 0.063405
13 2010 133523 0.061509
14 2011 132697 0.061129
15 2018 3533 0.001628

Conclusões:¶

  • Devido a limitações desse dataset, temos poucos registros do ano de 2018 porém é notável um equilíbrio de registros nos anos anteriores.
  • Considerando a quantidade de registros do ano de 2018 ser muito menor, ele será excluido das análises que envolverão a variável year.

Distribuição dos incidentes ao longo dos anos¶

In [57]:
# Agrupar os dados pela categoria de crime a cada ano
grouped_data = df.groupby(['category', 'year']).size().reset_index(name='count')

# Selecionar as top 10 categorias de crimes mais comuns
top_10_categories = df['category'].value_counts().nlargest(25).index
grouped_data = grouped_data[grouped_data['category'].isin(top_10_categories)]

# Definir uma paleta de 17 cores distintas
custom_colors = ['#636EFA', '#EF553B', '#00CC96', '#AB63FA', '#FFA15A', '#19D3F3', 
                 '#FF6692', '#B6E880', '#FF97FF', '#FECB52', '#1F77B4', '#FF7F0E', 
                 '#2CA02C', '#D62728', '#9467BD', '#8C564B', '#E377C2']

# Criar o gráfico de barras empilhadas com Plotly
fig = px.bar(grouped_data.sort_values(by='count', ascending=False), 
             x='category', 
             y='count',
             color='year', 
             title='Distribuição dos Incidentes ao longo dos anos',
             labels={'category': 'Categoria de Crime', 'count': 'Número de Incidentes', 'year': 'Anos'},
             color_discrete_sequence=custom_colors)  # Você pode escolher outras sequências de cores

# Personalizar o layout
fig.update_layout(barmode='stack',
                  height=650,
                  xaxis_title='Categoria de Crime',
                  yaxis_title='Número de Incidentes',
                  plot_bgcolor='white', 
                  yaxis=dict(showgrid=True, gridcolor='gray'))

# Exibir o gráfico
fig.show() 

## mudar cores
In [58]:
# Selecionar as top 10 categorias de crimes mais comuns
top_10_categories = df['category'].value_counts().nlargest(10).index

# Filtrar o DataFrame para essas categorias
filtered_data = df[df['category'].isin(top_10_categories)]

# Excluir o ano de 2018
filtered_data = filtered_data[filtered_data['year'] < 2018]

# Agrupar os dados por ano e categoria de crime
yearly_trends = filtered_data.groupby(['year', 'category']).size().reset_index(name='count')

# Criar o gráfico de linhas com Plotly
fig = px.line(yearly_trends.sort_values(by=['category', 'year'], ascending=False),
              x='year', 
              y='count', 
              color='category', 
              title='Tendência dos 10 Maiores Incidentes ao Longo dos Anos',
              labels={'year': 'Ano', 'count': 'Número de Incidentes', 'category': 'Categoria de Crime'})

# Personalizar o layout
fig.update_layout(height=500, 
                  xaxis_title='Ano', 
                  yaxis_title='Número de Incidentes',
                  legend_title_text='Categoria de Crime',
                  plot_bgcolor='white',  # Define o fundo do gráfico como branco
                  xaxis=dict(showgrid=True, gridcolor='gray'),  # Manter o grid do eixo x
                  yaxis=dict(showgrid=True, gridcolor='gray'))  # Manter o grid do eixo y

# Exibir o gráfico
fig.show()

Distribuição dos Incidentes ao longo dos Meses¶

In [60]:
import calendar
In [61]:
# Criar um mapeamento entre números e nomes dos meses
month_names = {i: calendar.month_name[i] for i in range(1, 13)}

# Agrupar os dados por mês e categoria de crime
monthly_data = df.groupby(['month', 'category']).size().reset_index(name='count')

# Substituir os números dos meses pelos nomes
monthly_data['month'] = monthly_data['month'].map(month_names)

# Criar o gráfico de barras empilhadas com nomes dos meses
fig = px.bar(monthly_data, 
             x='month', 
             y='count', 
             color='category',
             title='Distribuição de Incidentes por Mês',
             labels={'month': 'Mês', 'count': 'Número de Incidentes', 'category': 'Categoria de Crime'})

# Personalizar o layout
fig.update_layout(barmode='stack', 
                  height=500, 
                  xaxis_title='Mês', 
                  yaxis_title='Número de Incidentes',
                  legend_title_text='Categoria de Crime',
                  plot_bgcolor='white')

# Exibir o gráfico
fig.show()
In [62]:
# Agrupar os dados por mês
monthly_trend = df.groupby('month').size().reset_index(name='count')

# Substituir os números dos meses pelos nomes
monthly_trend['month'] = monthly_trend['month'].map(month_names)

# Criar o gráfico de linha com nomes dos meses
fig = px.line(monthly_trend, 
              x='month', 
              y='count', 
              title='Tendência de Incidentes por Mês',
              labels={'month': 'Mês', 'count': 'Número de Incidentes'})

# Personalizar o layout
fig.update_layout(height=500, 
                  xaxis_title='Mês', 
                  yaxis_title='Número de Incidentes',
                  plot_bgcolor='white',  # Define o fundo do gráfico como branco
                  xaxis=dict(showgrid=True, gridcolor='gray'),  # Remove o grid do eixo x
                  yaxis=dict(showgrid=True, gridcolor='gray'))  # Remove o grid do eixo y)

# Exibir o gráfico
fig.show()

Distribuição de Furtos ao Longo das Horas, Meses e Anos¶

  • Com esses gráficos será possível entender quais horários temos mais furtos, quais meses e anos foram tiveram um aumento ou diminuição de casos.
In [64]:
# Filtrar apenas furtos
theft_data = df[df['category'] == 'LARCENY/THEFT']

# Extrair a hora do timestamp
theft_data['hour'] = pd.to_datetime(theft_data['time']).dt.hour

# Contar o número de furtos por hora
hourly_theft = theft_data.groupby('hour').size().reset_index(name='count')

# Criar um gráfico de linha para mostrar a distribuição horária
fig = px.line(hourly_theft, x='hour', y='count',
              title='Distribuição de Furtos por Horário',
              labels={'hour': 'Hora do Dia', 'count': 'Número de Furtos'},
              markers=True)

# Personalizar o layout
fig.update_layout(height=400,
                  xaxis_title='Hora do Dia',
                  yaxis_title='Número de Furtos',
                  plot_bgcolor='white',  # Define o fundo do gráfico como branco
                  xaxis=dict(showgrid=True, gridcolor='gray'),  # Remove o grid do eixo x
                  yaxis=dict(showgrid=True, gridcolor='gray'))
fig.show()
In [65]:
# Contar o número de furtos por mês
monthly_theft = theft_data.groupby('month').size().reset_index(name='count')

# Criar um gráfico de linha para mostrar a distribuição mensal
fig = px.line(monthly_theft, x='month', y='count',
              title='Distribuição de Furtos ao Longo dos Meses',
              labels={'month': 'Mês', 'count': 'Número de Furtos'},
              markers=True)

# Personalizar o layout
fig.update_layout(height=400,
                  xaxis_title='Mês',
                  yaxis_title='Número de Furtos',
                  plot_bgcolor='white',  # Define o fundo do gráfico como branco
                  xaxis=dict(showgrid=True, gridcolor='gray'),  # Remove o grid do eixo x
                  yaxis=dict(showgrid=True, gridcolor='gray'))
fig.show()
In [66]:
# Contar o número de furtos por ano
yearly_theft = theft_data[theft_data['year'] < 2018].groupby('year').size().reset_index(name='count')

# Criar um gráfico de linha para mostrar a distribuição anual
fig = px.line(yearly_theft, x='year', y='count',
              title='Distribuição de Furtos ao Longo dos Anos',
              labels={'year': 'Ano', 'count': 'Número de Furtos'},
              markers=True)

# Personalizar o layout
fig.update_layout(height=400,
                  xaxis_title='Ano',
                  yaxis_title='Número de Furtos',
                  plot_bgcolor='white',  # Define o fundo do gráfico como branco
                  xaxis=dict(showgrid=True, gridcolor='gray'),  # Remove o grid do eixo x
                  yaxis=dict(showgrid=True, gridcolor='gray'))
fig.show()

Conclusões:¶

  • Em relação as horas, percebe-se que durante a madrugada o número de furtos diminui, muito provavelmente devido a maioria das pessoas já estarem em suas casas, porém a partir das 5 horas da manhã os valores começam a aumentar, notoriamente a partir das 16 horas o número entra em seu último ciclo de alta até o topo às 18 horas onde ocorreram 37 mil furtos.
  • Em relação aos meses, de de Junho a Agosto temos um aumento no número de furtos porém em Setembro volta a cair para novamente em Outubro termos o topo, com mais de 41 mil furtos.
  • Em relação aos anos de 2003 a 2010 percebe-se um momento de lateralização do gráfico em que o número de furtos se mantém estável sem nenhuma melhora ou piora relevante, porém de 2010 a 2017 inicia-se um ciclo de alta no número de furtos, o ano de 2018 devido a limitações na quantidade de registros foi excluído das análises.
In [68]:
df.head(1)
Out[68]:
unique_key pdid category descript resolution pddistrict address number street longitude latitude location timestamp dayofweek time day month year is_weekend
0 50688521 5068852128165 VANDALISM MALICIOUS MISCHIEF, GRAFFITI NONE PARK 1400 Block of WALLER ST 1400 WALLER -122.447968 37.768863 (37.768862804, -122.44796773714) 2005-06-16 00:01:00+00:00 Thursday 00:01:00 16 6 2005 0

Análises Geográficas¶

  • Utilizarei as features de latitude e longitude para criar mapas e realizarmos analises dos locais com maior número de incidências.

Mapa de Tendências Temporais¶

Esse mapa animado mostra como a distribuição de crimes muda ao longo do tempo.

In [71]:
# Criar o mapa animado ao longo do tempo
fig = px.scatter_mapbox(data_frame=df.sample(round(len(df)*0.01)), 
                        lat='latitude', 
                        lon='longitude', 
                        hover_name='category', 
                        hover_data=['descript', 'address'],
                        color='category',
                        animation_frame='year',
                        color_discrete_sequence=px.colors.qualitative.Dark24,
                        zoom=10, 
                        height=600)

# Configurar o mapa com estilo open-street-map
fig.update_layout(mapbox_style="open-street-map")
fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
fig.update_layout(height=800,  # Ajuste a altura conforme necessário
                  width=1200,  # Ajuste a largura conforme necessário
                  title=dict(text="Mapa de Calor de Crimes em São Francisco", x=0.5))
# Exibir o mapa
fig.show()

Conclusões:¶

  • Nesse mapa interativo é possível navegar separadamente ano a ano todos os incidentes que ocorreram na cidade e obter mais informações osbre sua categoria e descrições.

Mapa de Calor dos locais com maior criminalidade¶

Nesta etapa será criado um mapa de calor diretamente com as coordenadas de latitude e longitude, que não requer um GeoJSON.

Aqui está um exemplo de como criar um mapa de calor:

In [74]:
import plotly.express as px

# Criar o mapa de calor com Plotly
fig = px.density_mapbox(df.sample(round(len(df)*0.01)),
                        lat='latitude',
                        lon='longitude',
                        radius=10,
                        center={"lat": 37.7749, "lon": -122.4194},
                        zoom=10,
                        color_continuous_scale="Reds",
                        mapbox_style="open-street-map",  # Usar OpenStreetMap em vez de Mapbox
                        title="Mapa de Calor de Crimes em São Francisco")

# Ajustar o layout
fig.update_layout(
    height=800,  # Ajuste a altura conforme necessário
    width=1200,  # Ajuste a largura conforme necessário
    title=dict(text="Mapa de Calor de Crimes em São Francisco",
               x=0.5))
fig.show()

Conclusões:¶

  • Nesse mapa é possível notar diversos pontos de incidentes em crimes, a parte leste do mapa mostra uma quantidade bastante elevada de crimes se comparada a parte oeste, com um grande foco na parte nordeste, bairros como Civic Center, ruas como Turk Street e 6th Street diversos incidentes ocorreram ao longo dos anos.

Salvando alterações¶

In [77]:
df.to_csv('sfpd_incidents.csv', index=False)
In [78]:
df = pd.read_csv('sfpd_incidents.csv')
In [79]:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2170784 entries, 0 to 2170783
Data columns (total 19 columns):
 #   Column      Dtype  
---  ------      -----  
 0   unique_key  int64  
 1   pdid        int64  
 2   category    object 
 3   descript    object 
 4   resolution  object 
 5   pddistrict  object 
 6   address     object 
 7   number      object 
 8   street      object 
 9   longitude   float64
 10  latitude    float64
 11  location    object 
 12  timestamp   object 
 13  dayofweek   object 
 14  time        object 
 15  day         int64  
 16  month       int64  
 17  year        int64  
 18  is_weekend  int64  
dtypes: float64(2), int64(6), object(11)
memory usage: 314.7+ MB

5. Segmentando a base com K-Means¶

  • Nessa etapa irei realizar algumas features engineerings adicionais a título de buscar um algorítimo mais acurado;
  • Pré-processamentos;
  • Execução do algoritmo;
  • Visualização dos clusters;
  • Rotulação da base.
In [81]:
df.drop(['unique_key','pdid'], axis='columns', inplace=True)
In [82]:
df['timestamp'] = pd.to_datetime(df['timestamp'])
df['hour'] = df['timestamp'].dt.hour
In [83]:
def categorize_time(hour):
    if 0 <= hour < 6:
        return 'Early Morning'
    elif 6 <= hour < 12:
        return 'Morning'
    elif 12 <= hour < 18:
        return 'Afternoon'
    else:
        return 'Evening'

df['time_category'] = df['hour'].apply(categorize_time)
In [84]:
df.sample(1)
Out[84]:
category descript resolution pddistrict address number street longitude latitude location timestamp dayofweek time day month year is_weekend hour time_category
197573 OTHER OFFENSES MISCELLANEOUS INVESTIGATION NONE INGLESIDE 400 Block of ALEMANY BL Not Informed 400 Block of ALEMANY BL -122.416337 37.732413 (37.73241269219709, -122.41633673192958) 2016-04-08 22:16:00+00:00 Friday 22:16:00 8 4 2016 0 22 Evening
In [85]:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.cluster import KMeans
from sklearn.model_selection import GridSearchCV
from sklearn.impute import SimpleImputer
In [86]:
# 1. Pipeline de Pré-processamento
numeric_features = ['longitude', 'latitude', 'day', 'month', 'year', 'is_weekend', 'hour']
categorical_features = ['category', 'descript', 'resolution', 'pddistrict', 'dayofweek', 'time_category']

numeric_transformer = Pipeline(steps=[('imputer', SimpleImputer(strategy='median')),
                                      ('scaler', StandardScaler())])

categorical_transformer = Pipeline(steps=[('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
                                          ('onehot', OneHotEncoder(handle_unknown='ignore'))])

preprocessor = ColumnTransformer(transformers=[('numeric_transform', numeric_transformer, numeric_features),
                                               ('categoric_transform', categorical_transformer, categorical_features)])
In [87]:
df.isnull().sum()
Out[87]:
category         0
descript         0
resolution       0
pddistrict       0
address          0
number           0
street           0
longitude        0
latitude         0
location         0
timestamp        0
dayofweek        0
time             0
day              0
month            0
year             0
is_weekend       0
hour             0
time_category    0
dtype: int64
In [88]:
# Transformação dos dados antes de aplicar o KMeans
df_sample = df.sample(int(len(df) * 0.05))
df_transformed = preprocessor.fit_transform(df_sample)
In [89]:
# Converter matriz esparsa para matriz densa
df_dense = df_transformed.toarray()

# Verifique as dimensões da matriz densa
print("Dimensões da matriz densa:", df_dense.shape)
Dimensões da matriz densa: (108539, 819)
In [90]:
inertias = []
k_values = range(2, 10)

for k in k_values:
    kmeans = KMeans(n_clusters=k, n_init=10, random_state=42)
    kmeans.fit(df_dense)
    inertias.append(kmeans.inertia_)

# Plot do Elbow Method
plt.figure(figsize=(8, 6))
plt.plot(k_values, inertias, 'bx-')
plt.xlabel('Número de Clusters')
plt.ylabel('Inércia')
plt.title('Elbow Method para determinar o número ideal de clusters')
plt.show()
No description has been provided for this image
In [91]:
# Com base no gráfico, escolha um intervalo razoável para `n_clusters`, por exemplo:
optimal_k_range = range(2, 10)  # Ajuste conforme o "cotovelo" no gráfico

# 3. GridSearchCV com intervalo refinado de `n_clusters`
pipeline = Pipeline(steps=[('preprocessor', preprocessor),
                           ('kmeans', KMeans(n_init=10))])

param_grid = {'kmeans__n_clusters': optimal_k_range,  # Usa o intervalo refinado do Elbow Method
              'kmeans__init': ['k-means++', 'random'],  # Testa diferentes métodos de inicialização dos centroids
             'kmeans__max_iter': [300, 500]}  # Testa diferentes números máximos de iterações

grid_search = GridSearchCV(pipeline, param_grid, cv=5)
grid_search.fit(df)

# Melhor número de clusters e pipeline ajustado
print(f"Best number of clusters: {grid_search.best_params_['kmeans__n_clusters']}")
Best number of clusters: 9

Rotulando a base¶

  • Escolherei o número de clusters que foram recomendados pela tunagem de hiperparâmetros para rotular a base.
In [93]:
# Ajustar o preprocessor na amostra
preprocessed_sample = preprocessor.fit_transform(df_sample)

# Aplicar o KMeans com número de clusters recomendado
kmeans = KMeans(n_clusters=grid_search.best_params_['kmeans__n_clusters'],
                n_init=10,
                random_state=42)

kmeans.fit(preprocessed_sample)

# Pré-processar o DataFrame completo
preprocessed_full = preprocessor.transform(df)

# Rotular o DataFrame completo usando o modelo treinado na amostra
df['cluster_label'] = kmeans.predict(preprocessed_full)

# Visualizar os primeiros rótulos
print(df['cluster_label'].head())
0    2
1    8
2    3
3    4
4    0
Name: cluster_label, dtype: int32
In [94]:
df.to_csv('sfpd_incidents.csv', index=False)
In [2]:
from google.cloud import bigquery
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.graph_objects as go
import plotly.express as px
import plotly.colors as pc
#import scipy.stats as stats
import re

import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "composite-keel-371920-24c2737df989.json"

import warnings
warnings.filterwarnings('ignore')

# Configurando objetos Pandas DataFrame e Numpy Arrays para exibirem todas as informações
np.set_printoptions(threshold=np.inf)
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)

# Configurando estilo e paleta de cores que serão utilizadas nos gráficos
sns.set_theme(style="whitegrid", palette="viridis")
sns.color_palette('viridis')
Out[2]:
In [3]:
df = pd.read_csv('sfpd_incidents.csv')

6. Definição da Arquitetura, Criação da Rede e Execução do Modelo¶

Camada Neurônios Função de Ativação Observações
Entrada 60 - 60 Features de entrada
Primeira Oculta 256 ReLU Início com 256 neurônios
Segunda Oculta 128 ReLU Redução para 128 neurônios
Terceira Oculta 64 ReLU Redução para 64 neurônios
Quarta Oculta 32 ReLU Redução para 32 neurônios
Saída 9 Softmax Classificação Multiclasse

Função de Perda e Otimizador¶

  • Função de Perda: Sparse Categorical Crossentropy
  • Otimizador: Adam

Número de Epochs¶

  • Epochs: 50
In [6]:
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
In [7]:
df.sample(1)
Out[7]:
category descript resolution pddistrict address number street longitude latitude location timestamp dayofweek time day month year is_weekend hour time_category cluster_label
1301907 WARRANTS ENROUTE TO ADULT AUTHORITY ARREST, BOOKED BAYVIEW 200 Block of WILLIAMS AV 200 WILLIAMS -122.397744 37.729935 (37.7299346936044, -122.397744427103) 2011-10-21 19:24:00+00:00 Friday 19:24:00 21 10 2011 0 19 Evening 6

Redução de dimensionalidade¶

  • Para o encoding da variável categórica 'descript' irei aplicar um agrupamento por frequência para reduzirmos a quantidade de variáveis que serão passadas ao modelo, se eu não fizer isso o encoding irá criar 915 variáveis distintas para as descrições dos crimes e a execução do algorítmo será interrompida por falta de memória.
In [9]:
display(len(df['category'].value_counts()))
df['category'].value_counts().head(10)
39
Out[9]:
category
LARCENY/THEFT     467656
OTHER OFFENSES    304042
NON-CRIMINAL      233323
ASSAULT           190394
VEHICLE THEFT     125209
DRUG/NARCOTIC     118260
VANDALISM         113436
WARRANTS           99799
BURGLARY           89528
SUSPICIOUS OCC     78823
Name: count, dtype: int64
In [10]:
display(len(df['descript'].value_counts()))
df['descript'].value_counts().head(10)
915
Out[10]:
descript
GRAND THEFT FROM LOCKED AUTO             173078
LOST PROPERTY                             76517
BATTERY                                   66332
STOLEN AUTOMOBILE                         64062
DRIVERS LICENSE, SUSPENDED OR REVOKED     62124
WARRANT ARREST                            55460
AIDED CASE, MENTAL DISTURBED              55112
SUSPICIOUS OCCURRENCE                     51747
PETTY THEFT FROM LOCKED AUTO              50976
PETTY THEFT OF PROPERTY                   44655
Name: count, dtype: int64
In [11]:
# Definir um limiar para agrupar descrições raras

limiar_category = 75000  # Categorias com menos de 75000 ocorrências serão agrupadas
category_counts = df['category'].value_counts()
category_rare = category_counts[category_counts < limiar_category].index

# Substituir descrições raras por "Outras Descrições"
df['category_reduzido'] = df['category'].replace(category_rare, 'Outras Categorias')
In [12]:
# Definir um limiar para agrupar descrições raras

limiar_description = 50000  # Descrições com menos de 50000 ocorrências serão agrupadas
descriptions_counts = df['descript'].value_counts()
descriptions_rare = descriptions_counts[descriptions_counts < limiar_description].index

# Substituir descrições raras por "Outras Descrições"
df['descript_reduzido'] = df['descript'].replace(descriptions_rare, 'Outras Descrições')
In [13]:
categorical_columns = ['category_reduzido', 'resolution', 'pddistrict', 'dayofweek', 'time_category', 'descript_reduzido']
In [14]:
# Passo 1: One-Hot Encoding das colunas categóricas
df_encoded = pd.get_dummies(df, columns=categorical_columns, drop_first=True)
In [15]:
df_encoded.sample(3)
Out[15]:
category descript address number street longitude latitude location timestamp time day month year is_weekend hour cluster_label category_reduzido_BURGLARY category_reduzido_DRUG/NARCOTIC category_reduzido_LARCENY/THEFT category_reduzido_NON-CRIMINAL category_reduzido_OTHER OFFENSES category_reduzido_Outras Categorias category_reduzido_SUSPICIOUS OCC category_reduzido_VANDALISM category_reduzido_VEHICLE THEFT category_reduzido_WARRANTS resolution_ARREST, CITED resolution_CLEARED-CONTACT JUVENILE FOR MORE INFO resolution_COMPLAINANT REFUSES TO PROSECUTE resolution_DISTRICT ATTORNEY REFUSES TO PROSECUTE resolution_EXCEPTIONAL CLEARANCE resolution_JUVENILE ADMONISHED resolution_JUVENILE BOOKED resolution_JUVENILE CITED resolution_JUVENILE DIVERTED resolution_LOCATED resolution_NONE resolution_NOT PROSECUTED resolution_PROSECUTED BY OUTSIDE AGENCY resolution_PROSECUTED FOR LESSER OFFENSE resolution_PSYCHOPATHIC CASE resolution_UNFOUNDED pddistrict_CENTRAL pddistrict_INGLESIDE pddistrict_MISSION pddistrict_NORTHERN pddistrict_PARK pddistrict_RICHMOND pddistrict_SOUTHERN pddistrict_TARAVAL pddistrict_TENDERLOIN dayofweek_Monday dayofweek_Saturday dayofweek_Sunday dayofweek_Thursday dayofweek_Tuesday dayofweek_Wednesday time_category_Early Morning time_category_Evening time_category_Morning descript_reduzido_BATTERY descript_reduzido_DRIVERS LICENSE, SUSPENDED OR REVOKED descript_reduzido_GRAND THEFT FROM LOCKED AUTO descript_reduzido_LOST PROPERTY descript_reduzido_Outras Descrições descript_reduzido_PETTY THEFT FROM LOCKED AUTO descript_reduzido_STOLEN AUTOMOBILE descript_reduzido_SUSPICIOUS OCCURRENCE descript_reduzido_WARRANT ARREST
1477431 DRIVING UNDER THE INFLUENCE DRIVING WHILE UNDER THE INFLUENCE OF ALCOHOL VANNESS AV / CEDAR ST Not Informed VANNESS AV / CEDAR ST -122.421477 37.786153 (37.7861527352201, -122.421477162118) 2010-06-05 23:11:00+00:00 23:11:00 5 6 2010 1 23 1 False False False False False True False False False False True False False False False False False False False False False False False False False False False False False True False False False False False False True False False False False False True False False False False False True False False False False
323104 MISSING PERSON MISSING JUVENILE 3800 Block of 3RD ST 3800 3RD -122.387939 37.742260 (37.7422600519555, -122.387939452192) 2012-04-27 22:00:00+00:00 22:00:00 27 4 2012 0 22 6 False False False False False True False False False False False False False False False False False False False False True False False False False False False False False False False False False False False False False False False False False False True False False False False False True False False False False
1927523 SUSPICIOUS OCC INVESTIGATIVE DETENTION 300 Block of UNIVERSITY ST 300 UNIVERSITY -122.413830 37.725755 (37.72575457526155, -122.413830240899) 2017-03-16 23:09:00+00:00 23:09:00 16 3 2017 0 23 3 False False False False False False True False False False False False False False False False False False False False True False False False False False False False False False False False False False False False False False True False False False True False False False False False True False False False False
In [16]:
len(df_encoded.columns)
Out[16]:
69

Devido a dificuldades em tempo de execução, selecionarei 5% dos dados para a execução da rede neural¶

  • Aproximadamente 108 mil instâncias serão selecionadas aleatoriamente.
In [18]:
len(df_encoded.sample(int(len(df_encoded) * 0.05)))
Out[18]:
108539
In [19]:
df_encoded = df_encoded.sample(int(len(df_encoded) * 0.05))
In [20]:
# Definir X e y
X = df_encoded.drop(['cluster_label','category', 'descript', 'address', 'street', 'number', 'location', 'timestamp', 'time'], axis=1)
y = df_encoded['cluster_label']
In [21]:
from sklearn.model_selection import train_test_split
In [22]:
# Divisão em treino e teste
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
In [23]:
X_train.head(1)
Out[23]:
longitude latitude day month year is_weekend hour category_reduzido_BURGLARY category_reduzido_DRUG/NARCOTIC category_reduzido_LARCENY/THEFT category_reduzido_NON-CRIMINAL category_reduzido_OTHER OFFENSES category_reduzido_Outras Categorias category_reduzido_SUSPICIOUS OCC category_reduzido_VANDALISM category_reduzido_VEHICLE THEFT category_reduzido_WARRANTS resolution_ARREST, CITED resolution_CLEARED-CONTACT JUVENILE FOR MORE INFO resolution_COMPLAINANT REFUSES TO PROSECUTE resolution_DISTRICT ATTORNEY REFUSES TO PROSECUTE resolution_EXCEPTIONAL CLEARANCE resolution_JUVENILE ADMONISHED resolution_JUVENILE BOOKED resolution_JUVENILE CITED resolution_JUVENILE DIVERTED resolution_LOCATED resolution_NONE resolution_NOT PROSECUTED resolution_PROSECUTED BY OUTSIDE AGENCY resolution_PROSECUTED FOR LESSER OFFENSE resolution_PSYCHOPATHIC CASE resolution_UNFOUNDED pddistrict_CENTRAL pddistrict_INGLESIDE pddistrict_MISSION pddistrict_NORTHERN pddistrict_PARK pddistrict_RICHMOND pddistrict_SOUTHERN pddistrict_TARAVAL pddistrict_TENDERLOIN dayofweek_Monday dayofweek_Saturday dayofweek_Sunday dayofweek_Thursday dayofweek_Tuesday dayofweek_Wednesday time_category_Early Morning time_category_Evening time_category_Morning descript_reduzido_BATTERY descript_reduzido_DRIVERS LICENSE, SUSPENDED OR REVOKED descript_reduzido_GRAND THEFT FROM LOCKED AUTO descript_reduzido_LOST PROPERTY descript_reduzido_Outras Descrições descript_reduzido_PETTY THEFT FROM LOCKED AUTO descript_reduzido_STOLEN AUTOMOBILE descript_reduzido_SUSPICIOUS OCCURRENCE descript_reduzido_WARRANT ARREST
79002 -122.42202 37.76701 28 7 2006 0 15 False False True False False False False False False False False False False False False False False False False False True False False False False False False False True False False False False False False False False False False False False False False False False False True False False False False False False
In [24]:
X_train.shape
Out[24]:
(86831, 60)
In [25]:
import keras_tuner as kt
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from tensorflow.keras.optimizers import Adam
from sklearn.preprocessing import MinMaxScaler
from tensorflow.keras.callbacks import EarlyStopping, LambdaCallback
In [26]:
import keras_tuner as kt
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from tensorflow.keras.optimizers import Adam
from sklearn.preprocessing import MinMaxScaler
from tensorflow.keras.callbacks import EarlyStopping, LambdaCallback
In [ ]:
 
In [27]:
# Função para construir o modelo
def build_model(hp):
    model = Sequential()
    
    # Camada de entrada
    model.add(Dense(units=hp.Int('neurons', min_value=X_train.shape[1], max_value=X_train.shape[1], step=32),
                    activation='relu',
                    input_shape=(X_train.shape[1],)))

    # Primeira camada oculta com neurônios fixos e tunagem de dropout
    model.add(Dense(units=256, activation='relu'))
    if hp.Boolean('dropout_1'):
        model.add(Dropout(hp.Float('dropout_1', 0.2, 0.5, step=0.1)))

    # Segunda camada oculta
    model.add(Dense(units=128, activation='relu'))
    if hp.Boolean('dropout_2'):
        model.add(Dropout(hp.Float('dropout_2', 0.2, 0.5, step=0.1)))

    # Terceira camada oculta
    model.add(Dense(units=64, activation='relu'))
    if hp.Boolean('dropout_3'):
        model.add(Dropout(hp.Float('dropout_3', 0.2, 0.5, step=0.1)))

    # Quarta camada oculta
    model.add(Dense(units=32, activation='relu'))
    if hp.Boolean('dropout_4'):
        model.add(Dropout(hp.Float('dropout_4', 0.2, 0.5, step=0.1)))

    # Camada de saída
    model.add(Dense(units=9, activation='softmax'))

    # Compilação do modelo
    model.compile(optimizer=Adam(learning_rate=hp.Choice('learning_rate', values=[0.001, 0.0001, 0.00001])),
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])

    return model
In [28]:
# Definindo o tuner
tuner = kt.RandomSearch(build_model,
                        objective='val_accuracy',
                        max_trials=20,
                        executions_per_trial=5,
                        directory='my_dir',
                        project_name='cluster_classification')
Reloading Tuner from my_dir\cluster_classification\tuner0.json
In [29]:
# Callback para mostrar o último epoch
class PrintLastEpoch(LambdaCallback):
    def on_epoch_end(self, epoch, logs=None):
        if epoch == self.params['epochs'] - 1:
            print(f'\nMétricas Finais Encontradas:')
            print(f'Epoch {epoch + 1}, Loss: {logs["loss"]}, Accuracy: {logs["accuracy"]}, Val Loss: {logs["val_loss"]}, Val Accuracy: {logs["val_accuracy"]}.')
In [30]:
# Função para mostrar os melhores hiperparâmetros
def best_hyperparameters(best_hps):
    print(f"A busca por hiperparâmetros está completa.") 
    print(f"O número ideal de unidades na primeira camada densamente conectada é {best_hps.get('neurons')}.") 
    print(f"A taxa de aprendizado ideal é {best_hps.get('learning_rate')}.")
    
    # Verifica a existência dos parâmetros de dropout antes de tentar acessá-los
    if 'dropout_1' in best_hps.values:
        print(f"Dropout na primeira camada: {best_hps.get('dropout_1')}")
    if 'dropout_2' in best_hps.values:
        print(f"Dropout na segunda camada: {best_hps.get('dropout_2')}")
    if 'dropout_3' in best_hps.values:
        print(f"Dropout na terceira camada: {best_hps.get('dropout_3')}")
    if 'dropout_4' in best_hps.values:
        print(f"Dropout na quarta camada: {best_hps.get('dropout_4')}")
In [31]:
# Realizando a busca pelos melhores hiperparâmetros
tuner.search(X_train, y_train,
             epochs=50,
             validation_data=(X_test, y_test),
             verbose=1,
             callbacks=[EarlyStopping(monitor='val_loss', patience=15), PrintLastEpoch()])

# Obtendo os melhores hiperparâmetros
best_hps = tuner.get_best_hyperparameters(num_trials=1)[0]

# Print dos melhores hiperparâmetros
best_hyperparameters(best_hps)
A busca por hiperparâmetros está completa.
O número ideal de unidades na primeira camada densamente conectada é 480.
A taxa de aprendizado ideal é 0.0001.
In [32]:
# Treinando o modelo final com os melhores hiperparâmetros
model = tuner.hypermodel.build(best_hps)
history = model.fit(X_train, y_train,
                    epochs=100,
                    validation_data=(X_test, y_test),
                    verbose=1,
                    callbacks=[EarlyStopping(monitor='val_loss', patience=15), PrintLastEpoch()])
Epoch 1/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 4s 1ms/step - accuracy: 0.2335 - loss: 2.9639 - val_accuracy: 0.3603 - val_loss: 1.5597
Epoch 2/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.5672 - loss: 1.2069 - val_accuracy: 0.7001 - val_loss: 0.8155
Epoch 3/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.6828 - loss: 0.8225 - val_accuracy: 0.7639 - val_loss: 0.6149
Epoch 4/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7274 - loss: 0.6794 - val_accuracy: 0.7541 - val_loss: 0.5639
Epoch 5/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7383 - loss: 0.6409 - val_accuracy: 0.7446 - val_loss: 0.5841
Epoch 6/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7436 - loss: 0.6231 - val_accuracy: 0.7592 - val_loss: 0.5595
Epoch 7/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7553 - loss: 0.5862 - val_accuracy: 0.7467 - val_loss: 0.5699
Epoch 8/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7571 - loss: 0.5795 - val_accuracy: 0.7558 - val_loss: 0.5656
Epoch 9/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7594 - loss: 0.5695 - val_accuracy: 0.7283 - val_loss: 0.6269
Epoch 10/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7619 - loss: 0.5569 - val_accuracy: 0.7367 - val_loss: 0.5990
Epoch 11/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7661 - loss: 0.5426 - val_accuracy: 0.7466 - val_loss: 0.6185
Epoch 12/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7629 - loss: 0.5387 - val_accuracy: 0.7758 - val_loss: 0.4915
Epoch 13/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7679 - loss: 0.5208 - val_accuracy: 0.7608 - val_loss: 0.5318
Epoch 14/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7662 - loss: 0.5171 - val_accuracy: 0.7692 - val_loss: 0.5319
Epoch 15/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7714 - loss: 0.5072 - val_accuracy: 0.7737 - val_loss: 0.4775
Epoch 16/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7683 - loss: 0.5027 - val_accuracy: 0.7801 - val_loss: 0.4805
Epoch 17/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7749 - loss: 0.4917 - val_accuracy: 0.7848 - val_loss: 0.4533
Epoch 18/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7736 - loss: 0.4897 - val_accuracy: 0.7717 - val_loss: 0.4986
Epoch 19/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7776 - loss: 0.4791 - val_accuracy: 0.7734 - val_loss: 0.4969
Epoch 20/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7740 - loss: 0.4800 - val_accuracy: 0.7848 - val_loss: 0.4640
Epoch 21/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7769 - loss: 0.4781 - val_accuracy: 0.7722 - val_loss: 0.5155
Epoch 22/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7758 - loss: 0.4765 - val_accuracy: 0.7778 - val_loss: 0.4794
Epoch 23/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7786 - loss: 0.4656 - val_accuracy: 0.7795 - val_loss: 0.4899
Epoch 24/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7831 - loss: 0.4597 - val_accuracy: 0.7790 - val_loss: 0.4685
Epoch 25/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7780 - loss: 0.4606 - val_accuracy: 0.7745 - val_loss: 0.4793
Epoch 26/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7831 - loss: 0.4572 - val_accuracy: 0.7816 - val_loss: 0.4582
Epoch 27/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7824 - loss: 0.4569 - val_accuracy: 0.7871 - val_loss: 0.4379
Epoch 28/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7838 - loss: 0.4580 - val_accuracy: 0.7902 - val_loss: 0.4530
Epoch 29/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7855 - loss: 0.4489 - val_accuracy: 0.7822 - val_loss: 0.4447
Epoch 30/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7826 - loss: 0.4536 - val_accuracy: 0.7868 - val_loss: 0.4421
Epoch 31/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7850 - loss: 0.4487 - val_accuracy: 0.7626 - val_loss: 0.5001
Epoch 32/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7891 - loss: 0.4467 - val_accuracy: 0.7873 - val_loss: 0.4385
Epoch 33/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7889 - loss: 0.4436 - val_accuracy: 0.7866 - val_loss: 0.4459
Epoch 34/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7847 - loss: 0.4448 - val_accuracy: 0.7879 - val_loss: 0.4389
Epoch 35/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7876 - loss: 0.4428 - val_accuracy: 0.7836 - val_loss: 0.4513
Epoch 36/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7880 - loss: 0.4378 - val_accuracy: 0.7848 - val_loss: 0.4424
Epoch 37/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7891 - loss: 0.4390 - val_accuracy: 0.7810 - val_loss: 0.4472
Epoch 38/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7902 - loss: 0.4364 - val_accuracy: 0.7852 - val_loss: 0.4370
Epoch 39/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7866 - loss: 0.4393 - val_accuracy: 0.7914 - val_loss: 0.4349
Epoch 40/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7886 - loss: 0.4391 - val_accuracy: 0.7899 - val_loss: 0.4572
Epoch 41/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7908 - loss: 0.4319 - val_accuracy: 0.7963 - val_loss: 0.4212
Epoch 42/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7903 - loss: 0.4334 - val_accuracy: 0.7927 - val_loss: 0.4422
Epoch 43/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7912 - loss: 0.4311 - val_accuracy: 0.7989 - val_loss: 0.4167
Epoch 44/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7908 - loss: 0.4312 - val_accuracy: 0.7949 - val_loss: 0.4265
Epoch 45/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7951 - loss: 0.4267 - val_accuracy: 0.7958 - val_loss: 0.4186
Epoch 46/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7943 - loss: 0.4256 - val_accuracy: 0.7916 - val_loss: 0.4267
Epoch 47/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7895 - loss: 0.4288 - val_accuracy: 0.7927 - val_loss: 0.4224
Epoch 48/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7936 - loss: 0.4255 - val_accuracy: 0.7927 - val_loss: 0.4367
Epoch 49/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7914 - loss: 0.4259 - val_accuracy: 0.7952 - val_loss: 0.4340
Epoch 50/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7955 - loss: 0.4195 - val_accuracy: 0.7760 - val_loss: 0.4461
Epoch 51/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7934 - loss: 0.4242 - val_accuracy: 0.7977 - val_loss: 0.4214
Epoch 52/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7937 - loss: 0.4249 - val_accuracy: 0.7933 - val_loss: 0.4358
Epoch 53/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7964 - loss: 0.4194 - val_accuracy: 0.7996 - val_loss: 0.4213
Epoch 54/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7939 - loss: 0.4224 - val_accuracy: 0.7886 - val_loss: 0.4559
Epoch 55/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7977 - loss: 0.4190 - val_accuracy: 0.7931 - val_loss: 0.4419
Epoch 56/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7988 - loss: 0.4165 - val_accuracy: 0.7990 - val_loss: 0.4193
Epoch 57/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7973 - loss: 0.4179 - val_accuracy: 0.7999 - val_loss: 0.4114
Epoch 58/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7991 - loss: 0.4136 - val_accuracy: 0.7938 - val_loss: 0.4249
Epoch 59/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7978 - loss: 0.4181 - val_accuracy: 0.7936 - val_loss: 0.4332
Epoch 60/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7982 - loss: 0.4134 - val_accuracy: 0.8038 - val_loss: 0.4126
Epoch 61/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8010 - loss: 0.4144 - val_accuracy: 0.7800 - val_loss: 0.4690
Epoch 62/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7980 - loss: 0.4133 - val_accuracy: 0.8066 - val_loss: 0.4038
Epoch 63/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8016 - loss: 0.4066 - val_accuracy: 0.8006 - val_loss: 0.4343
Epoch 64/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8021 - loss: 0.4120 - val_accuracy: 0.8025 - val_loss: 0.4119
Epoch 65/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8016 - loss: 0.4086 - val_accuracy: 0.7840 - val_loss: 0.4344
Epoch 66/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8003 - loss: 0.4119 - val_accuracy: 0.8009 - val_loss: 0.4135
Epoch 67/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8051 - loss: 0.4043 - val_accuracy: 0.7987 - val_loss: 0.4153
Epoch 68/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8056 - loss: 0.4037 - val_accuracy: 0.8091 - val_loss: 0.4009
Epoch 69/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8055 - loss: 0.4024 - val_accuracy: 0.8091 - val_loss: 0.4085
Epoch 70/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8060 - loss: 0.4001 - val_accuracy: 0.8007 - val_loss: 0.4134
Epoch 71/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8053 - loss: 0.4032 - val_accuracy: 0.8063 - val_loss: 0.4009
Epoch 72/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8051 - loss: 0.4016 - val_accuracy: 0.8055 - val_loss: 0.4079
Epoch 73/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8077 - loss: 0.3978 - val_accuracy: 0.8076 - val_loss: 0.4099
Epoch 74/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8104 - loss: 0.3995 - val_accuracy: 0.8168 - val_loss: 0.3898
Epoch 75/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8109 - loss: 0.3946 - val_accuracy: 0.8204 - val_loss: 0.3833
Epoch 76/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8112 - loss: 0.3903 - val_accuracy: 0.8081 - val_loss: 0.3975
Epoch 77/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8107 - loss: 0.3959 - val_accuracy: 0.8162 - val_loss: 0.3893
Epoch 78/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8138 - loss: 0.3871 - val_accuracy: 0.7972 - val_loss: 0.4146
Epoch 79/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8149 - loss: 0.3924 - val_accuracy: 0.8160 - val_loss: 0.3961
Epoch 80/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8181 - loss: 0.3854 - val_accuracy: 0.8326 - val_loss: 0.3718
Epoch 81/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8203 - loss: 0.3796 - val_accuracy: 0.8168 - val_loss: 0.3856
Epoch 82/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8236 - loss: 0.3779 - val_accuracy: 0.7992 - val_loss: 0.4049
Epoch 83/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8227 - loss: 0.3755 - val_accuracy: 0.7674 - val_loss: 0.4344
Epoch 84/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8254 - loss: 0.3710 - val_accuracy: 0.7886 - val_loss: 0.4343
Epoch 85/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8277 - loss: 0.3673 - val_accuracy: 0.8416 - val_loss: 0.3499
Epoch 86/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8279 - loss: 0.3679 - val_accuracy: 0.8155 - val_loss: 0.3794
Epoch 87/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8356 - loss: 0.3552 - val_accuracy: 0.8241 - val_loss: 0.3685
Epoch 88/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8395 - loss: 0.3517 - val_accuracy: 0.8612 - val_loss: 0.3324
Epoch 89/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8537 - loss: 0.3281 - val_accuracy: 0.8907 - val_loss: 0.2859
Epoch 90/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8583 - loss: 0.3191 - val_accuracy: 0.7870 - val_loss: 0.4276
Epoch 91/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8680 - loss: 0.3031 - val_accuracy: 0.9091 - val_loss: 0.2579
Epoch 92/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8736 - loss: 0.2905 - val_accuracy: 0.9030 - val_loss: 0.2490
Epoch 93/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8837 - loss: 0.2744 - val_accuracy: 0.9107 - val_loss: 0.2345
Epoch 94/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8825 - loss: 0.2717 - val_accuracy: 0.9017 - val_loss: 0.2425
Epoch 95/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8865 - loss: 0.2613 - val_accuracy: 0.7242 - val_loss: 0.7785
Epoch 96/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8898 - loss: 0.2576 - val_accuracy: 0.9286 - val_loss: 0.2017
Epoch 97/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8908 - loss: 0.2631 - val_accuracy: 0.8844 - val_loss: 0.2525
Epoch 98/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8957 - loss: 0.2513 - val_accuracy: 0.8239 - val_loss: 0.3608
Epoch 99/100
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8921 - loss: 0.2502 - val_accuracy: 0.8663 - val_loss: 0.2788
Epoch 100/100
2703/2714 ━━━━━━━━━━━━━━━━━━━━ 0s 992us/step - accuracy: 0.8962 - loss: 0.2449
Métricas Finais Encontradas:
Epoch 100, Loss: 0.24812816083431244, Accuracy: 0.895912766456604, Val Loss: 0.31046974658966064, Val Accuracy: 0.8446195125579834.
2714/2714 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.8962 - loss: 0.2450 - val_accuracy: 0.8446 - val_loss: 0.3105
In [33]:
# Plotando a perda (loss)
plt.plot(history.history['loss'], label='Training Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

# Plotando a acurácia
plt.plot(history.history['accuracy'], label='Training Accuracy')
plt.plot(history.history['val_accuracy'], label='Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.show()
No description has been provided for this image
No description has been provided for this image
In [34]:
# Predições
predictions = model.predict(X_test)

# Convertendo as predições para classes
predicted_classes = predictions.argmax(axis=1)
print(predicted_classes[:5])
679/679 ━━━━━━━━━━━━━━━━━━━━ 0s 536us/step
[3 6 7 1 6]
In [35]:
from sklearn.metrics import classification_report, confusion_matrix
In [36]:
# Matriz de Confusão
matriz_confusao = confusion_matrix(y_test, predicted_classes)
plt.figure(figsize=(6,5))
sns.heatmap(matriz_confusao, annot=True, fmt='d', cmap='RdYlGn', annot_kws={'fontsize': 15})

# Mostrando o relatório de classificação
print(classification_report(y_test, predicted_classes))
              precision    recall  f1-score   support

           0       0.99      0.62      0.77      3103
           1       0.98      0.97      0.97      3538
           2       0.95      0.95      0.95      1983
           3       0.63      0.98      0.77      2948
           4       0.98      0.98      0.98      2000
           5       1.00      0.33      0.50         3
           6       0.74      0.96      0.83      2993
           7       0.81      0.85      0.83      2128
           8       0.99      0.51      0.67      3012

    accuracy                           0.84     21708
   macro avg       0.90      0.80      0.81     21708
weighted avg       0.88      0.84      0.84     21708

No description has been provided for this image

8. Avaliando os Modelos: Sumários, Métricas e Conclusões.¶

Gostaria de primeiramente agradecer a Deus por poder realizar mais esse projeto e conhecimento adquirido, a você que viu esse projeto e está lendo essas conclusões, foi um projeto desafiador e incrívelmente satisfatório de se realizar, me senti motivado a cada nova descoberta e pensando nas possibilidades do que pode-se fazer de análises com esses dados e que definitivamente evoluiu de maneira notória minhas habilidades.

Trabalhar com mais de 2 milhões de instâncias é um desafio, tive diversos problemas, tanto em execução de código como compatibilidade de bibliotecas, mas me sinto motivado pois sei que momentos assim são comuns em minha futura carreira e sou do tipo que aprende e fixa esse aprendizado melhor na prática, tendo erros e corrigindo-os.

Analisar profundamente dados de uma área de conhecimento nova e a união de um algorítmo não-supervisionado com um supervisionado era idéia central desse projeto e foi concluída com sucesso. Em algumas análises tive que escolher amostras aleatórias dos dados devido a diversos problemas de tempo de execução, no entanto, análises geográficas, temporais e distribuição foram realizadas com base nas variáveis já disponíveis e feature engineering.

Na etapa de machine learning enfrentei o maior desafio desse projeto: Redução de Dimensionalidade, por se tratar de diversos crimes, diversas descrições e resoluções para esses crimes, estamos falando de milhares de diferentes categorias, que devem ser encodadas para serem entendidas pelo algoritmo. Em algumas abordagens iniciais os dados de treino tinham por volta de 1000 variáveis criadas após o encoding, e fui testando diferentes maneiras de reduzir a quantidade de variáveis que seriam enviadas ao modelo para evitar erros de falta de memória para execução ou tempo excessivo de execução.

Os dados foram clusterizados e rotulados com o K-Means (9 clusters distintos geraram 9 rótulos distintos), decidi por criar Pipelines para os pré-processamentos necessários pois facilita e gera um código clean-code, a tunagem de hiperparâmetros foi utilizada com diferentes métodos de inicialização dos centroides e número máximo de iterações juntamente com o método elbow para encontrarmos o melhor número de clusters.

Com a base rotulada uma rede neural foi criada para classificar esses rótulos e ser capaz de rotular novos incidentes. Esta rede neural foi elaborada também com tunagem de hiperparâmetros (Learning Rate - Dropout Rate), camadas de Dropout e Early Stopping e 100 epochs(iterações). Por se tratar de uma classificação multiclasse (9 classes) e as variáveis terem sido encodadas OneHot, a Sparse Categorical Cross Entropy foi utilizada.

Por fim gráficos mostrando o aumento taxa de acurácia e diminuição da perda a cada iteração foram criados e métricas de classificação foram geradas para a análise dos resultados. Tivemos resultados de mais de 80% de acurácia média com algumas classes chegando a valores acima de 90% das métricas para uma abordagem inicial considero que foi um bom resultado que é passível de melhoras dependendo de quais classes necessitam ser previstas, lembrando que algumas foram excluidas devido a falta de representatividade.

Obrigado novamente,

Gustavo

In [ ]: